/*
 * ViewportComponent.java
 *
 * Created on September 7, 2006, 3:04 PM
 *
 * (C) 2006 R. Alexander Milowski
 */

package com.smallx.xproc.library;

import com.smallx.xpath.XPathMatcher;
import com.smallx.xpath.XPathMatcherFactory;
import com.smallx.xproc.ComponentType;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.logging.Level;
import org.infoset.component.Component;
import org.infoset.component.ComponentBase;
import org.infoset.xml.Document;
import org.infoset.xml.Item;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.Named;
import org.infoset.xml.Parent;
import org.infoset.xml.XMLException;
import org.infoset.xml.filter.IgnoreDocumentFilter;
import org.infoset.xml.util.DocumentSource;

/**
 *
 * @author alex
 */
public class ViewportComponent extends ComponentBase {

   ComponentType type;
   Name inputName;
   Name outputName;
   Name innerInputName;
   Name innerOutputName;
   XPathMatcherFactory expression;
   URI inputLocation;
   Document inputDocument;
   ItemDestination output;
   
   List<ComponentInitializer> components;
   
   /** Creates a new instance of ViewportComponent */
   public ViewportComponent(ComponentType type,Name innerInputName,Name innerOutputName,List<ComponentInitializer> components) {
      super(type.getName());
      this.type = type;
      this.innerInputName = innerInputName;
      this.innerOutputName = innerOutputName;
      this.components = components;
      inputName = type.getInputPortNames().iterator().next();
      outputName = type.getOutputPortNames().iterator().next();
      inputLocation = null;
      inputDocument = null;
   }
   
   public void init(Component.Context context) 
      throws XMLException
   {
      super.init(context);
      for (ComponentInitializer initializer : components) {
         initializer.setParentContext(context);
         initializer.init();
         initializer.bindStatics();
         initializer.bindOutputs();
      }
   }
   
   public void setExpression(XPathMatcherFactory exp) {
      this.expression = exp;
   }
   
   public List<ComponentInitializer> getComponents() {
      return components;
   }
   
   public void bindResource(Name port, URI location) throws XMLException {
      if (!port.equals(inputName)) {
         super.bindResource(port,location);
      }
      inputLocation = null;
   }
   
   public void bindResource(Name port, Document doc) throws XMLException {
      if (!port.equals(inputName)) {
         super.bindResource(port,inputDocument);
         return;
      }
      inputDocument = doc;
   }
   
   
   public void bindOutputPort(Name port, ItemDestination output) throws XMLException {
      if (!port.equals(outputName)) {
         super.bindOutputPort(port,output);
      }
      this.output = output;
   }
   
   public void onStart()
      throws XMLException
   {
      if (inputDocument!=null) {
         DocumentSource.generate(inputDocument,false,getDestination());
      } else if (inputLocation!=null) {
         try {
            context.getDocumentLoader().generate(inputLocation,getDestination());
         } catch (IOException ex) {
            throw new XMLException("Cannot load static document reference "+inputLocation+" for port "+inputName+" on step "+type.getName());
         }
      }
   }
   
   public ItemDestination getInputPort(Name port) 
      throws XMLException
   {
      if (!port.equals(inputName)) {      
         return super.getInputPort(port);
      }
      return getDestination();
   }

   public void onDocumentInput(Name port, Document doc) 
      throws XMLException 
   {
      if (!port.equals(inputName)) {      
         super.onDocumentInput(port,doc);
         return;
      }
      DocumentSource.generate(doc,false,getDestination());
   }
   
   protected ItemDestination getDestination() {
      return new ItemDestination() {
          XPathMatcher matcher = expression.newMatcher();
          int sentLevel = -1;
          ItemConstructor currentConstructor = null;
          ItemDestination inner;
          public void send(Item item) 
             throws XMLException
          {
            if (matcher.match(item)!=null) {
               if (context.getLog().isLoggable(Level.FINER)) {
                  String name = "";
                  if (item instanceof Named) {
                     name = ((Named)item).getName().toString();
                  }
                  context.getLog().info("Matched "+item.getType()+" "+name);
               }
               if (sentLevel<0 && item.getType()!=Item.ItemType.DocumentItem) {
                  currentConstructor = context.getInfoset().createItemConstructor();
                  URI base = item instanceof Parent ? 
                     ((Parent)item).getBaseURI() : 
                     null;
                  processMatch(currentConstructor.createDocument(base));
               }
               switch (item.getType()) {
                  case DocumentItem:
                     sentLevel++;
                     break;
                  case ElementItem:
                     sentLevel++;
                     if (sentLevel==0) {
                        // TODO: this shouldn't be need in streaming.  All elements should have localized declarations.
                        //((Element)item).localizeNamespaceDeclarations();
                     }
                     break;
                  case ElementEndItem:
                  case DocumentEndItem:
                     sentLevel--;
               }
               processMatch(item);
               if (sentLevel<0 && item.getType()!=Item.ItemType.DocumentEndItem) {
                  processMatch(currentConstructor.createDocumentEnd());
               }
            } else {
               post(item);
            }
             
          }
          
          /*
           * Send the non-matches to the output
           */
          void post(Item item)
             throws XMLException
          {
             output.send(item);
          }
          
          /*
           * Send the matches to the inner steps
           */
          void processMatch(Item item)
             throws XMLException
          {
             if (inner==null) {
                // Bind the output of the last component
                ComponentInitializer last = components.get(components.size()-1);
                last.rebindOutput(new DestinationAdapter(innerOutputName,new IgnoreDocumentFilter(output)));
                
                // Tell the inner components we're going to start
                for (ComponentInitializer initializer : components) {
                   initializer.getComponent().onStart();
                }
                
                ComponentInitializer first = components.get(0);
                inner = first.getComponent().getInputPort(innerInputName);
                
             }
             inner.send(item);
             if (item.getType()==Item.ItemType.DocumentEndItem) {
                inner = null;
                // Tell the inner components we're done
                for (ComponentInitializer initializer : components) {
                   initializer.getComponent().onEnd();
                }
             }
          }
         
      };
   }
   
}
